package hos.map.coordinate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import hos.map.area.AreaCalculator;
import hos.map.area.AreaCalculatorInterface;
import hos.map.distance.CollectionDistanceCalculatorInterface;
import hos.map.distance.DistanceCalculator;
import hos.map.distance.LengthUnit;
import hos.map.geometry.Polygon;
import hos.map.geometry.Polyline;
import hos.map.tool.CalculatorUtils;
import hos.map.tool.MathUtil;


/**
 * <p>Title: LatLngCollection </p>
 * <p>Description: 点的集合 </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2023-12-10 22:26
 */
public class LatLngCollection extends ArrayList<LatLng> implements AreaCalculatorInterface, CollectionDistanceCalculatorInterface {

    private final CoordinateType type;
    private DistanceCalculator mDistanceCalculator;
    private AreaCalculator mAreaCalculator;

    /**
     * 从map中获取经纬度
     */
    public static LatLngCollection fromMap(List<Map<String, Double>> list, CoordinateType type) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        List<LatLng> latLngList = new ArrayList<>();
        for (Map<String, Double> map : list) {
            latLngList.add(LatLng.fromMap(map, type));
        }
        return new LatLngCollection(latLngList);
    }

    /**
     * 从map中获取经纬度
     */
    public static LatLngCollection fromMapLatLng(List<Map<String, Object>> list) {
        return fromMapLatLng(list, null);
    }

    /**
     * 从map中获取经纬度
     */
    public static LatLngCollection fromMapLatLng(List<Map<String, Object>> list, CoordinateType type) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        List<LatLng> latLngList = new ArrayList<>();
        for (Map<String, Object> map : list) {
            latLngList.add(LatLng.fromMapLatLng(map, type));
        }
        return new LatLngCollection(latLngList);
    }

    public LatLngCollection(CoordinateType type) {
        this.type = type;
    }

    public LatLngCollection(LatLng... points) {
        this(Arrays.asList(points), (CoordinateType) null);
    }

    public LatLngCollection(Collection<LatLng> points) {
        this(points, (CoordinateType) null);
    }

    public LatLngCollection(Collection<LatLng> points, CoordinateType type) {
        this(parseType(points, type));
        addAll(points);
    }

    private static CoordinateType parseType(Collection<LatLng> collection, CoordinateType type) {
        if (type != null) {
            return type;
        }
        if (collection == null || collection.isEmpty()) {
            return CoordinateType.wgs84;
        }
        for (LatLng point : collection) {
            if (point != null) {
                return point.getType();
            }
        }
        return CoordinateType.wgs84;
    }

    public CoordinateType getType() {
        return type;
    }

    public boolean add(double y, double x) {
        return add(new LatLng(y, x, type));
    }

    public void add(int index, double y, double x) {
        add(index, new LatLng(y, x, type));
    }

    /**
     * 根据下标移除点位
     *
     * @param index 下标
     * @return 移除的点位
     */
    public LatLng removeAt(int index) {
        if (isEmpty() || index >= size()) {
            return null;
        }
        return remove(index);
    }

    /**
     * 获取第一个点位
     */
    public LatLng first() {
        if (isEmpty()) {
            return null;
        }
        return get(0);
    }

    /**
     * 移除第一个点位并返回
     */
    public LatLng removeFirst() {
        if (isEmpty()) {
            return null;
        }
        return removeAt(0);
    }

    /**
     * 获取最后一个点位
     */
    public LatLng last() {
        if (isEmpty()) {
            return null;
        }
        return get(size() - 1);
    }

    /**
     * 移除最后一个点位并返回
     */
    public LatLng removeLast() {
        if (isEmpty()) {
            return null;
        }
        return removeAt(size() - 1);
    }

    /**
     * 将集合中的点位顺序进行翻转
     */
    public LatLngCollection asReversed() {
        Collections.reverse(this);
        return new LatLngCollection(this, getType());
    }

    public LatLng[] toArray() {
        return super.toArray(new LatLng[0]);
    }

    /**
     * 设置面积计算
     */
    public void setAreaCalculator(AreaCalculator mAreaCalculator) {
        this.mAreaCalculator = mAreaCalculator;
    }

    /**
     * 设置距离计算
     */
    public void setDistanceCalculator(DistanceCalculator mDistanceCalculator) {
        this.mDistanceCalculator = mDistanceCalculator;
    }

    /**
     * 获取点集合形成多变形的面积
     */
    @Override
    public double area() {
        if (mAreaCalculator != null) {
            return mAreaCalculator.area(this);
        }
        return CalculatorUtils.area(this);
    }

    /**
     * 获取点集合形成线段的距离
     */
    @Override
    public double distance() {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.distance(this);
        }
        return CalculatorUtils.distanceVincenty(this);
    }

    /**
     * 转换为面对象
     */
    public Polyline toPolyline() {
        return new Polyline(this, getType());
    }

    /**
     * 转换为线对象
     */
    public Polygon toPolygon() {
        return new Polygon(this, getType());
    }

    /**
     * 转换成边界
     */
    public LatLngBounds toLatLngBounds() {
        return new LatLngBounds(this);
    }

    /**
     * 判断点位是否在集合形成的面内部
     *
     * @param point 目标点位
     * @return true 在多边形的内部
     */
    @Override
    public boolean isPolygonContains(LatLng point) {
        return CalculatorUtils.isPolygonContains(this, point);
    }

    @Override
    public double distance(LengthUnit unit) {
        if (mDistanceCalculator != null) {
            return mDistanceCalculator.distance(unit, this);
        }
        return CalculatorUtils.distanceVincenty(unit, this);
    }

    /**
     * 计算多点的中心点
     */
    public LatLng getCenter() {
        if (isEmpty()) {
            return null;
        }
        if (size() == 1) {
            return get(0);
        }
        if (size() == 2) {
            LatLng latLng1 = get(0);
            LatLng latLng2 = get(1);
            return latLng1.getCenter(latLng2);
        }
        double X = 0.0;
        double Y = 0.0;
        double Z = 0.0;

        double lat, lon, hyp;
        for (LatLng point : this) {
            lat = point.getLatitudeInRad();
            lon = point.getLongitudeInRad();

            X += Math.cos(lat) * Math.cos(lon);
            Y += Math.cos(lat) * Math.sin(lon);
            Z += Math.sin(lat);
        }

        final int nrOfCoordinates = size();
        X = X / nrOfCoordinates;
        Y = Y / nrOfCoordinates;
        Z = Z / nrOfCoordinates;

        lon = Math.atan2(Y, X);
        hyp = Math.sqrt(X * X + Y * Y);
        lat = Math.atan2(Z, hyp);
        double latRound = MathUtil.round(MathUtil.radianToDeg(lat));
        double lonRound = MathUtil.round(MathUtil.radianToDeg(lon));
        return new LatLng(latRound, lonRound, getType());
    }

    public List<Map<String, Object>> toData() {
        List<Map<String, Object>> list = new ArrayList<>();
        for (LatLng latLng : this) {
            list.add(latLng.toMap());
        }
        return list;
    }

    public String toJson() {
        StringBuilder builder = new StringBuilder();
        builder.append("[");
        for (LatLng latLng : this) {
            if (builder.length() > 1) {
                builder.append(",");
            }
            builder.append(latLng.toJson());
        }
        builder.append("]");
        return builder.toString();
    }
}
